using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision;
using FarseerPhysics.Factories;
using FarseerPhysics.Common;
using FarseerPhysics.Common.Decomposition;
using FarseerPhysics.Common.ConvexHull;


namespace EpicBall
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        GraphicsDevice device;

        int screenWidth;
        int screenHeight;

        Texture2D collisionTexture;
        Texture2D ballTexture;
        Texture2D blankTexture;
        Texture2D waveTexture;
        Texture2D playerTopArrowTexture;

        World gameWorld;
        Body player, ground, levelCollision, waveBody;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            graphics.PreferredBackBufferWidth = 800;
            graphics.PreferredBackBufferHeight = 600;
            graphics.IsFullScreen = false;
            graphics.ApplyChanges();
            Window.Title = "Epic Ball - Alpha 0.0.2";

            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            device = graphics.GraphicsDevice;
            screenWidth = device.PresentationParameters.BackBufferWidth;
            screenHeight = device.PresentationParameters.BackBufferHeight;

            collisionTexture = Content.Load<Texture2D>("test_level");
            ballTexture = Content.Load<Texture2D>("ball1");
            blankTexture = Content.Load<Texture2D>("blank");
            waveTexture = Content.Load<Texture2D>("wave1");
            playerTopArrowTexture = Content.Load<Texture2D>("player_top_arrow");

            //Create new world with gravity 10 (9.8)
            gameWorld = new World(Vector2.UnitY * 10);

            //Create the player
            player = BodyFactory.CreateBody(gameWorld);
            FixtureFactory.AttachCircle(ConvertUnits.ToSimUnits(10), 25, player);
            player.BodyType = BodyType.Dynamic;
            //player.Position = new Vector2(240, 50);
            player.Position = ConvertUnits.ToSimUnits(new Vector2(240, 50));

            //Create the ground
            ground = BodyFactory.CreateBody(gameWorld);
            Fixture groundFixture = FixtureFactory.AttachRectangle(ConvertUnits.ToSimUnits(screenWidth), ConvertUnits.ToSimUnits(16), 10, Vector2.Zero, ground);
            SlideCollision(ground);
            ground.Position = ConvertUnits.ToSimUnits(new Vector2(screenWidth/2, 500));


            Vector2 origin = new Vector2(waveTexture.Width/2,waveTexture.Height/2);
            waveBody = ImageToPolygonBody(waveTexture, gameWorld, 1f, 1f, ref origin);
            waveBody.Position = ConvertUnits.ToSimUnits(new Vector2(screenWidth / 2, 400));
            NormalCollision(waveBody);

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            //Step through time in game
            CheckInput(gameTime);
            adjustPlayerSpeed();
            gameWorld.Step(Math.Min((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000, (1f / 30f)));
            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            // TODO: Add your drawing code here
            spriteBatch.Begin();
            //This works
            //spriteBatch.Draw(ballTexture, new Rectangle((int)(player.Position.X - 15), (int)(player.Position.Y - 15), 30, 30), Color.Gray);

            DrawPlayer();
            DrawLevel();


            spriteBatch.End();
            base.Draw(gameTime);
        }

        //Take an image and turn it into a polygon in the world
        public static Body ImageToPolygonBody(Texture2D texture, World world, float density, float scale_, ref Vector2 polygonOrigin)
        {
            //Create an array to hold the data from the texture
            uint[] data = new uint[(texture.Width) * (texture.Height)];

            //Collect data from bitmap
            texture.GetData(data);

            //Create Polygon from Bitmap
            Vertices verts = PolygonTools.CreatePolygon(data, (texture.Width), false);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            Vector2 scale = new Vector2(ConvertUnits.ToSimUnits(scale_), ConvertUnits.ToSimUnits(scale_));
            verts.Scale(ref scale);

            //Make sure that the origin of the texture is the centroid (real center of geometry)
            polygonOrigin = verts.GetCentroid();

            //Translate the polygon so that it aligns properly with centroid.
            Vector2 vertsTranslate = -polygonOrigin;
            verts.Translate(ref vertsTranslate);

            //We simplify the vertices found in the texture.
            //verts = SimplifyTools.ReduceByDistance(verts, 4f);

            //Decompose polygon into smaller chuncks that Farseer can process better
            List<Vertices> list;
            list = BayazitDecomposer.ConvexPartition(verts);
            //list = EarclipDecomposer.ConvexPartition(verts);

            //Create a body
            return BodyFactory.CreateCompoundPolygon(world, list, density);
        }

        //For objects that act as normal collision
        protected void NormalCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.9f;
        }

        //For objects that add to the balls bounces
        protected void AdditionCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 1.3f;
        }

        //For objects that dampen the balls bounce
        protected void SubtractiveCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.6f;
        }

        //For objects that take away all bounce
        protected void SlideCollision(Body b)
        {
            b.Friction = 0.5f;
            b.IsStatic = true;
            b.Restitution = 0.0f;
        }

        //Control the player by adding force to the ball
        protected virtual void CheckInput(GameTime gameTime)
        {

            KeyboardState keyState = Keyboard.GetState();
            KeyboardState oldState;
            int forcePower = 600;
            //Apply force in the arrow key direction
            Vector2 force = Vector2.Zero;
            if (keyState.IsKeyDown(Keys.Left))
            {
                force.X -= forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            if (keyState.IsKeyDown(Keys.Right))
            {
                force.X += forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            if (keyState.IsKeyDown(Keys.Up))
            {
                force.Y -= 2*forcePower * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
            if (keyState.IsKeyDown(Keys.Down))
            {

                player.Position = new Vector2(0, 0);
            }

            player.ApplyForce(ref force);

            oldState = keyState;
        }


        //Simple way to control the players max speed (threshold)
        protected void adjustPlayerSpeed()
        {
            int threshold = 10;
            float distanceMoved = Vector2.Distance(Vector2.Zero, player.LinearVelocity);
            if (distanceMoved > threshold)
            {
                Vector2 direction = player.LinearVelocity;
                direction.Normalize();
                player.LinearVelocity = (direction * threshold);
            }
            //player.LinearDamping = 0.5f;
        }

        protected void DrawPlayer()
        {
            Vector2 ballDisplayUnits = ConvertUnits.ToDisplayUnits(player.Position);
            spriteBatch.Draw(ballTexture, new Rectangle((int)(ballDisplayUnits.X - 10), (int)(ballDisplayUnits.Y - 10), 20, 20), Color.Gray);
            //spriteBatch.Draw(ballTexture, new Rectangle((int)(ballDisplayUnits.X - 15), (int)(ballDisplayUnits.Y - 15), 30, 30), null, Color.Gray, player.Position.X, new Vector2(0,-15), SpriteEffects.None, 0);

            //If the player is above the screen, draw a small arrow to indicate position
            if (ballDisplayUnits.Y < 0)
            {
                spriteBatch.Draw(playerTopArrowTexture, new Rectangle((int)ballDisplayUnits.X, 0, playerTopArrowTexture.Width, playerTopArrowTexture.Height), Color.Red);
            }
        }

        //Draws all the objects within each level.
        //Todo: Accept an integer and load all objects within that level.
        protected void DrawLevel()
        {
            Vector2 groundDisplayUnits = ConvertUnits.ToDisplayUnits(ground.Position);
            spriteBatch.Draw(blankTexture, new Rectangle((int)(groundDisplayUnits.X - screenWidth / 2), (int)(groundDisplayUnits.Y - 8), screenWidth, 16), Color.Green);

            Vector2 waveDisplayUnits = ConvertUnits.ToDisplayUnits(waveBody.Position);
            spriteBatch.Draw(waveTexture, new Rectangle((int)(waveDisplayUnits.X - 316), (int)(waveDisplayUnits.Y - 82), waveTexture.Width, waveTexture.Height), Color.White);

        }
        
    }
}
